package com.guli.seckill.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.guli.common.constant.MqConstant;
import com.guli.common.to.SeckillOrderTo;
import com.guli.common.utils.R;
import com.guli.common.vo.MemberRespVo;
import com.guli.seckill.constant.SeckillConstant;
import com.guli.seckill.exception.IsBoughtException;
import com.guli.seckill.feign.ProductFeignService;
import com.guli.seckill.feign.SeckillFeignService;
import com.guli.seckill.interceptor.LoginUserInterceptor;
import com.guli.seckill.service.SeckillService;
import com.guli.seckill.to.SeckillSkuRedisTo;
import com.guli.seckill.vo.SeckillSessionWithSkus;
import com.guli.seckill.vo.SkuInfoVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * @Author zzb
 * @Date 2024/4/24 15:03
 * @Version 1.0
 */
@Service
@Slf4j
public class SeckillServiceImpl implements SeckillService {
    @Autowired
    private SeckillFeignService seckillFeignService;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private ProductFeignService productFeignService;
    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private RabbitTemplate rabbitTemplate;

    /**
     * 上架3天后秒杀的商品
     * xxl-job定时调用
     */
    @Override
    public void uploadSeckillSku3Days() {
        //获取三天后活动场次安排
        R session = seckillFeignService.getLatest3DaySession();
        if (session.getCode() == 0) {
            List<SeckillSessionWithSkus> sessionWithSkuses =
                    session.getData(new TypeReference<List<SeckillSessionWithSkus>>() {
                    });
            //缓存活动信息
            this.saveSessionInfos(sessionWithSkuses);
            //缓存获取的关联商品信息
            this.saveSessionSkuInfos(sessionWithSkuses);
        }
    }

    /**
     * 获取当前可秒杀的商品
     * @return
     */
    @Override
    public List<SeckillSkuRedisTo> getCurrentSeckillSkus() {
        //确定当前时间属于哪个秒杀场次
        long time = new Date().getTime();
        Set<String> keys = stringRedisTemplate.keys(SeckillConstant.SECKILL_SESSIONS + "*");
        if (keys == null) {
            return null;
        }
        for (String key : keys) {
            String[] strings = key.split("_");
            //获取秒杀开始时间
            long start = Long.parseLong(strings[0]);
            //获取秒杀结束时间
            long end = Long.parseLong(strings[1]);
            //当前时间为秒杀活动时间
            if (time >= start && time <= end) {
                List<String> keyList = stringRedisTemplate.opsForList().range(key, -100, 100);
                if (CollectionUtils.isEmpty(keyList)) {
                    log.info("{} => 该场次下暂无秒杀商品", key);
                    continue;
                }
                BoundHashOperations<String, String, String> hashOps = stringRedisTemplate.boundHashOps(SeckillConstant.SECKILL_SKUS);
                List<String> valueList = hashOps.multiGet(keyList);
                if (valueList != null) {
                    List<SeckillSkuRedisTo> seckillSkuRedisToList = valueList.stream().map(seckillSkuRedisToItem -> {
                        SeckillSkuRedisTo seckillSkuRedisTo = JSON.parseObject(seckillSkuRedisToItem, SeckillSkuRedisTo.class);
                        seckillSkuRedisTo.setRandomCode(null);  //当前秒杀开始就需要随机码
                        return seckillSkuRedisTo;
                    }).collect(Collectors.toList());
                    return seckillSkuRedisToList;
                }
            }
            break;
        }
        return null;
    }

    /**
     * 获取秒杀商品的信息
     * @param skuId
     * @return
     */
    @Override
    public SeckillSkuRedisTo getSkuSeckillInfo(Long skuId) {
        //找到所有需要参与秒杀的商品的key
        BoundHashOperations<String, String, String> hashOps =
                stringRedisTemplate.boundHashOps(SeckillConstant.SECKILL_SESSIONS);
        Set<String> keys = hashOps.keys();
        if (keys == null || keys.size() <= 0) {
            return null;
        }
        String regx = "\\d_" + skuId;
        for (String key : keys) {
            if (Pattern.matches(regx, key)) {
                String str = hashOps.get(key);
                SeckillSkuRedisTo seckillSkuRedisTo = JSON.parseObject(str, SeckillSkuRedisTo.class);
                if (seckillSkuRedisTo == null) {
                    return null;
                }
                //随机码
                long current = new Date().getTime();
                Long startTime = seckillSkuRedisTo.getStartTime();
                Long endTime = seckillSkuRedisTo.getEndTime();
                if (current < startTime || current > endTime) {
                    seckillSkuRedisTo.setRandomCode(null);
                }
                return seckillSkuRedisTo;
            }
        }
        return null;
    }

    /**
     * 秒杀商品
     * @param killId
     * @param key
     * @param num
     * @return
     */
    @Override
    public String kill(String killId, String key, Integer num) {
        MemberRespVo memberRespVo = LoginUserInterceptor.threadLocal.get();
        //获取当前秒杀商品的详细信息
        BoundHashOperations<String, String, String> hashOps = stringRedisTemplate.boundHashOps(SeckillConstant.SECKILL_SKUS);
        String str = hashOps.get(killId);
        if (StringUtils.isEmpty(str)) {
            return null;
        }
        SeckillSkuRedisTo seckillSkuRedisTo = JSON.parseObject(str, SeckillSkuRedisTo.class);
        //校验合法性
        Long startTime = seckillSkuRedisTo.getStartTime();
        Long endTime = seckillSkuRedisTo.getEndTime();
        long time = new Date().getTime();
        long ttl = endTime - time;
        //当前不是秒杀时间
        if (time < startTime || time > endTime) {
            throw new SecurityException("秒杀未开始或已过期");
        }
        //当前时间为秒杀时间  获取校验随机码
        String randomCode = seckillSkuRedisTo.getRandomCode();
        //获取
        String skuIdWithPromotionSessionId = seckillSkuRedisTo.getPromotionSessionId() + "_" + seckillSkuRedisTo.getSkuId();
        //校验秒杀随机码
        if (!randomCode.equals(key) || !skuIdWithPromotionSessionId.equals(killId)) {
            log.info("key 或 killId错误");
            return null;
        }

        //获取用户编号
        Long userId = memberRespVo.getId();
        //获取场次 + skuId
        String redisKey = userId + "_" + skuIdWithPromotionSessionId;
        //分布式锁验证这个人是否已经买过  幂等性；如果只要秒杀成功，就去占位
        Boolean isBought = stringRedisTemplate.opsForValue().setIfAbsent(redisKey, num.toString(), ttl, TimeUnit.MILLISECONDS);
        if (Boolean.TRUE.equals(isBought)) {
            throw new IsBoughtException("用户已经购买过该商品");
        }
        //用户为购买过商品
        //获取信息量
        RSemaphore semaphore = redissonClient.getSemaphore(SeckillConstant.SECKILL_STOCK + randomCode);
        //获取信号量
        boolean isAcquire = semaphore.tryAcquire(num);
        if (isAcquire) {
            //秒杀成功 获取订单号
            String orderSn = IdWorker.getTimeId();
            SeckillOrderTo seckillOrderTo = new SeckillOrderTo();
            seckillOrderTo.setOrderSn(orderSn);
            seckillOrderTo.setMemberId(memberRespVo.getId());
            seckillOrderTo.setNum(num);
            seckillOrderTo.setPromotionSessionId(seckillSkuRedisTo.getPromotionSessionId());
            seckillOrderTo.setSkuId(seckillSkuRedisTo.getSkuId());
            seckillOrderTo.setSeckillPrice(seckillSkuRedisTo.getSeckillPrice());
            rabbitTemplate.convertAndSend(MqConstant.ORDER_EVENT_EXCHANGE, MqConstant.ORDER_SECKILL_ROUTING, seckillOrderTo);
            return orderSn;
        }
        return null;
    }

    /**
     * 缓存活动信息
     * @param sessionWithSkus
     */
    private void saveSessionInfos(List<SeckillSessionWithSkus> sessionWithSkus) {
        if (sessionWithSkus == null) {
            log.info("3天后暂无秒杀活动场次");
            return;
        }
        sessionWithSkus.forEach(session -> {
            String key = SeckillConstant.SECKILL_SESSIONS + session.getStartTime() + "_" + session.getEndTime();
            List<String> longList = session.getRelationEntities()
                    .stream()
                    .map(item -> item.getPromotionSessionId() + "_" + item.getSkuId().toString())
                    .collect(Collectors.toList());
            Boolean hasKey = stringRedisTemplate.hasKey(key);
            if (Boolean.FALSE.equals(hasKey)) {
                stringRedisTemplate.opsForList().leftPushAll(key, longList);
            }
        });
    }

    /**
     * 缓存获取的关联商品信息
     * 每个关联的商品信息
     * @param sessionWithSkus
     */
    private void saveSessionSkuInfos(List<SeckillSessionWithSkus> sessionWithSkus) {
        if (sessionWithSkus == null) {
            log.info("三天后暂无秒杀商品");
            return;
        }
        sessionWithSkus.forEach(sessionWithSku -> {
            //准备哈希操作
            BoundHashOperations<String, String, Object> hashOps = stringRedisTemplate.boundHashOps(SeckillConstant.SECKILL_SKUS);
            if (sessionWithSku.getRelationEntities() == null) {
                log.info("{}  => 该秒杀活动场次暂无关联商品", sessionWithSku.getId());
                return;
            }
            sessionWithSku.getRelationEntities().forEach(sessionWithSkuItem -> {
                //生成秒杀商品随机码
                String scekillToken = UUID.randomUUID().toString().replace("-", "");
                if (Boolean.FALSE.equals(hashOps.hasKey(sessionWithSkuItem.getPromotionSessionId()
                        + "_" + sessionWithSkuItem.getSkuId().toString()))) {
                    SeckillSkuRedisTo seckillSkuRedisTo = new SeckillSkuRedisTo();
                    //sku的基本数据
                    R result = productFeignService.skuInfo(sessionWithSkuItem.getSkuId());
                    if (result.getCode() == 0) {
                        SkuInfoVo skuInfoVo = result.getData(new TypeReference<SkuInfoVo>() {
                        });
                        seckillSkuRedisTo.setSkuInfoVo(skuInfoVo);
                    }
                    //sku秒杀信息
                    BeanUtils.copyProperties(sessionWithSkuItem, seckillSkuRedisTo);
                    //设置当前商品的秒杀时间信息
                    seckillSkuRedisTo.setStartTime(sessionWithSku.getStartTime().getTime());
                    seckillSkuRedisTo.setEndTime(sessionWithSku.getEndTime().getTime());
                    //随机码
                    seckillSkuRedisTo.setRandomCode(scekillToken);
                    //seckill:stock:   //商品随机码
                    String jsonString = JSON.toJSONString(seckillSkuRedisTo);
                    //将该场次秒杀的商品放入缓存
                    hashOps.put(sessionWithSkuItem.getPromotionSessionId().toString() + "_" + sessionWithSkuItem.getSkuId().toString(), jsonString);
                    //使用库存作为分布式的信号量 限流
                    RSemaphore semaphore = redissonClient.getSemaphore(SeckillConstant.SECKILL_STOCK + scekillToken);
                    //商品可以秒杀的数量作为信号量
                    semaphore.trySetPermits(sessionWithSkuItem.getSeckillCount().intValue());
                }
            });
        });
    }
}
